#include "pool.h"

using namespace PGSTD;
using namespace pqxx;

/**
 * Constructor - initializes a number of connections (defined in the config) and the mutex object
 */
ConnectionPool::ConnectionPool(const Config& init_cfg) {
    pthread_mutex_init(&pool_list_mutex, NULL);
    cfg = init_cfg;

    int n = min(cfg.init_conn_pool, cfg.max_conn_pool);
    
    for(int i = 0; i < n; ++i) {
      connection* curr = create_db_conn();
      if(curr == NULL) {
        close_all_conn(); // Close all opened connections before throwing the exception.
        
        log::error("Can not create %d initial number of connections.", n);

        throw broken_connection("Can not create initial number of connections.");
      }
      
      log::info("Initial connection %d successfully created.", i+1);

      put_connection(curr); // Add the connection to the pool.
    }
}


/**
 * Destroys the mutex object and closes all connections
 * 
 */
ConnectionPool::~ConnectionPool() {
    
    pthread_mutex_destroy(&pool_list_mutex);
    
    close_all_conn();
}

connection* ConnectionPool::create_db_conn() {
    string conn_info = "";
    if(cfg.host) conn_info += "host=" + string(cfg.host) + " ";
    if(cfg.port) conn_info += "port=" + string(cfg.port) + " ";
    if(cfg.user) conn_info += "user=" + string(cfg.user) + " ";
    if(cfg.password) conn_info += "password=" + string(cfg.password) + " ";
    if(cfg.database) conn_info += "dbname=" + string(cfg.database);

    connection* new_conn;
    try {
        new_conn = new connection(conn_info);
    }
    catch (const broken_connection &e){
        log::error(e.what());
        new_conn = NULL;
    }
    catch(const exception &e) {
        log::error(e.what());
        new_conn = NULL;
    }
    
    if(new_conn != NULL) 
        log::info("Connected with connection string: %s", conn_info.c_str());    

    return new_conn;
}

void ConnectionPool::delete_db_conn(connection* conn) {
    if(conn != NULL) delete conn;
}

/**
 * Closes all opened connections and frees the pool_list.
 * 
 */
void ConnectionPool::close_all_conn() {

    
    list<connection*>::iterator it = pool_list.begin();    
    while(it != pool_list.end()) {
        connection* conn = *it;
        it = pool_list.erase(it);
        delete_db_conn(conn);
    }
 
}

/**
 * If there are any availabe connections in the pool one is returned, else a new connestion is created and returned
 * 
 */
connection* ConnectionPool::get_connection() {
    connection* conn = NULL;

    pthread_mutex_lock(&pool_list_mutex);    
    if(pool_list.size() > 0) {
       conn = pool_list.front();
       pool_list.pop_front();
    }
    else {
        pthread_mutex_unlock(&pool_list_mutex);
        
        log::debug("ConnectionPool::get_connection() created a new connection");
        return create_db_conn();     
    }
    pthread_mutex_unlock(&pool_list_mutex);
 
    log::debug("ConnectionPool::get_connection() reused an old connection.");
    log::debug("ConnectionPool has %d more connections.", pool_list.size());

    return conn;
}

/**
 * This function checks if the maximum allowed number of connections in the pool is reached, and returnes a connection to
 * the pool or destroys it 
 * 
 */
void ConnectionPool::put_connection(connection* conn) {
    pthread_mutex_lock(&pool_list_mutex);
    
    if((int)pool_list.size() == cfg.max_conn_pool) {        
        pthread_mutex_unlock(&pool_list_mutex);
        
        log::debug("ConnectionPool::put_connection() deleted a connection, connection pool is full");
        delete_db_conn(conn);
        return ;
    }
    
    pool_list.push_back(conn);

    pthread_mutex_unlock(&pool_list_mutex);

    log::debug("ConnectionPool::put_connection() put a connection in the pool.");
    log::debug("ConnectionPool has %d more connections.", pool_list.size());

}
